<!doctype html>
<html>
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" href="https://fav.farm/🛰️" />
    <title>Custom element • Remote DOM</title>
  </head>

  <body>
    <div id="root"></div>

    <!-- The “remote” environment, which we will render in the element above -->
    <iframe id="remote-iframe" src="/remote.html" hidden></iframe>

    <script type="module">
      class UIButton extends HTMLElement {
        // By default, `DOMRemoteReceiver` will assign remote properties as properties,
        // but only if the element has a matching property defined. Otherwise, the remote
        // properties will be set as attributes. We’ll observe the `primary` attribute
        // in order to update our rendered content when that attribute changes. We’ll
        // define a `onClick` method, though, which will be set to the value of the `onClick`
        // remote property.
        static get observedAttributes() {
          return ['primary'];
        }

        connectedCallback() {
          const primary = this.hasAttribute('primary') ?? false;

          const root = this.attachShadow({mode: 'open'});

          // We render a <slot> where we want the element’s children to go.
          root.innerHTML = `
            <style>
              .Button {
                appearance: none;
                font-size: 1rem;
                padding: 0.5rem 1rem;
                background: transparent;
                border: 1px solid gray;
                border-radius: 0.5rem;
              }

              .Button--primary {
                background: blue;
                color: white;
              }
            </style>
            <button class="Button"><slot></slot></button>
          `;

          if (primary) {
            root.querySelector('.Button').classList.add('Button--primary');
          }
        }

        attributeChangedCallback(name, oldValue, newValue) {
          if (name === 'primary') {
            const button = this.shadowRoot?.querySelector('.Button');

            if (button == null) return;

            if (newValue == null) {
              button.classList.remove('Button--primary');
            } else {
              button.classList.add('Button--primary');
            }
          }
        }

        // Remote DOM will automatically call methods on a custom element to satisfy
        // remote method calls.
        focus() {
          this.shadowRoot.querySelector('button').focus();
        }
      }

      customElements.define('ui-button', UIButton);
    </script>

    <script type="module">
      import {DOMRemoteReceiver} from '@remote-dom/core/receivers';
      import {ThreadIframe} from '@quilted/threads';

      const root = document.querySelector('#root');
      const iframe = document.querySelector('#remote-iframe');

      // In earlier examples, we did not pass any arguments, which allows the DOM
      // receiver to mirror any element it receives. By passing the `elements` option,
      // we are restricting the allowed elements to only the ones we list, which in this
      // case means only our `ui-button` element can be rendered.
      const receiver = new DOMRemoteReceiver({
        elements: ['ui-button'],
      });
      receiver.connect(root);

      // We use the `@quilted/threads` library to create a “thread” for our iframe,
      // which lets us communicate over `postMessage` without having to worry about
      // most of its complexities. This includes the ability to send functions between
      // environments, which we rely on for the `click` event listener.
      const thread = new ThreadIframe(iframe);

      // We will call the `render` method on the thread, which will send the iframe
      // the `receiver.connection` object. This object, called a `RemoteConnection`,
      // allows the remote environment to synchronize its tree of UI elements into
      // the `root` element we connected our `receiver` to above.
      thread.imports.render(receiver.connection);
    </script>
  </body>
</html>
